共通データ¶
時系列データなら何でもよいので、下記のデータを利用した
pd.read_csv() はURLから直接CSVデータをダウンロードできる
https://www.mhlw.go.jp/stf/covid-19/open-data.html
47都道府県ではデータが多すぎて見づらいので、東京・大阪・名古屋のみにする
import pandas as pd
url = "https://covid19.mhlw.go.jp/public/opendata/newly_confirmed_cases_daily.csv"
data = pd.read_csv(url)
data = data.melt(id_vars="Date")
data.columns = ["Date", "Prefecture", "Newly confirmed cases"]
query = "Prefecture == 'Tokyo' or Prefecture == 'Osaka' or Prefecture == 'Aichi'"
data = data.query(query)
data.iloc[200:210,]
| Date | Prefecture | Newly confirmed cases | |
|---|---|---|---|
| 15917 | 2020/8/3 | Tokyo | 258 |
| 15918 | 2020/8/4 | Tokyo | 309 |
| 15919 | 2020/8/5 | Tokyo | 263 |
| 15920 | 2020/8/6 | Tokyo | 360 |
| 15921 | 2020/8/7 | Tokyo | 461 |
| 15922 | 2020/8/8 | Tokyo | 429 |
| 15923 | 2020/8/9 | Tokyo | 331 |
| 15924 | 2020/8/10 | Tokyo | 197 |
| 15925 | 2020/8/11 | Tokyo | 188 |
| 15926 | 2020/8/12 | Tokyo | 222 |
たいていのライブラリは日付が str のままでは時系列処理できないので Timestamp に変換する
(Seaborn は str でも時系列グラフを描画できた)
Date = pd.to_datetime(data["Date"])
data2 = data.copy()
data2["Date"] = Date
print(type(data.iloc[0,0]))
print(type(data2.iloc[0,0]))
data2.iloc[200:210,] #見た目は同じ
<class 'str'> <class 'pandas._libs.tslibs.timestamps.Timestamp'>
| Date | Prefecture | Newly confirmed cases | |
|---|---|---|---|
| 15917 | 2020-08-03 | Tokyo | 258 |
| 15918 | 2020-08-04 | Tokyo | 309 |
| 15919 | 2020-08-05 | Tokyo | 263 |
| 15920 | 2020-08-06 | Tokyo | 360 |
| 15921 | 2020-08-07 | Tokyo | 461 |
| 15922 | 2020-08-08 | Tokyo | 429 |
| 15923 | 2020-08-09 | Tokyo | 331 |
| 15924 | 2020-08-10 | Tokyo | 197 |
| 15925 | 2020-08-11 | Tokyo | 188 |
| 15926 | 2020-08-12 | Tokyo | 222 |
data_yoko = data2.pivot(index="Date",
columns="Prefecture",
values="Newly confirmed cases")
data_yoko
| Prefecture | Aichi | Osaka | Tokyo |
|---|---|---|---|
| Date | |||
| 2020-01-16 | 0 | 0 | 0 |
| 2020-01-17 | 0 | 0 | 0 |
| 2020-01-18 | 0 | 0 | 0 |
| 2020-01-19 | 0 | 0 | 0 |
| 2020-01-20 | 0 | 0 | 0 |
| ... | ... | ... | ... |
| 2023-05-04 | 221 | 499 | 994 |
| 2023-05-05 | 252 | 440 | 906 |
| 2023-05-06 | 282 | 430 | 1062 |
| 2023-05-07 | 863 | 1098 | 2345 |
| 2023-05-08 | 425 | 547 | 1331 |
1209 rows × 3 columns
高水準作図ライブラリ¶
Seaborn¶
Seaborn は matplotlib のラッパー。データフレームからシンプルな関数で様々なグラフを描画できる
色遣いがきれいで、デフォルトでもカッコよいグラフが描けるのが特徴。
グラフごとに微妙に作法が違っていたりして、使い込むとやや洗練されていない印象を受ける。
足りない部分は matplotlib で調整できる/する必要があるのが、長所でもあり短所でもある。
また、デフォルトでは日本語が文字化けするのが面倒。
# minimum
import seaborn as sns
ax = sns.lineplot(data=data2,
x="Date",
y="Newly confirmed cases",
hue="Prefecture")
#practical
import seaborn as sns
import matplotlib.pyplot as plt
# 日本語文字化け対応でフォントを指定する。matplotlib に比べれば楽。
sns.set(font='Meiryo')
# 画像サイズを設定する程度の事で matplotlib を呼ぶのが面倒
# グラフ描画より前に画像サイズを設定する必要があるなど、matplotlib のお作法に縛られる
plt.figure(figsize=[14,4])
# グラフ描画の関数はシンプルでとっつきやすい
ax = sns.lineplot(data=data2,
x="Date",
y="Newly confirmed cases",
hue="Prefecture")
ax.set_title("Covid19 日次感染者数")
plt.xticks(rotation=-90)
plt.show()
# plt.legend(bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0.)
Seaborn Object¶
2022年に導入された Seaborn のグラフィック指向インターフェイス。
ざっくり言って、Seaborn で ggplot/plotnine 的な書き方ができるようになる。
minimum¶
seaborn に含まれているので、新たに別ライブラリをインストールしないで済むのは嬉しい
ワンライナーだとかえって複雑に見えて有難味が分からないが……
#minimum
import seaborn.objects as so
so.Plot(data2, x="Date", y="Newly confirmed cases", color="Prefecture").add(so.Line())
Practical¶
実務的な設定をしようと思うと見通しが格段に良くなって有難味が分かる。
- pros
- 関数呼び出しのカッコごとに改行してメソッドチェーンすると読みやすい。
- グラフサイズが Seaborn 内で設定できるようになったのが嬉しい(元々できてろよという気もするが…)
- cons
- 依然としてデフォルトでは日本語非対応
- 日本語フォントの設定は matplotlib の rc 設定を弄っており、おまじないレベルだがちょっと面倒くさい。
import seaborn.objects as so
so.Plot(
data2, x="Date", y="Newly confirmed cases", color="Prefecture",
).add(
so.Line()
).layout(
size=(14,5)
).label(
title="COVID19 日次感染者数",
x="日付",
y="新規感染者数"
).theme(
{"font.family":"Meiryo"}
)
minimum¶
最小構成ではやはり有難味が分かりにくい
from plotnine import *
# データとプロット対象を指定
g = ggplot(data2, aes(x="Date", y="Newly confirmed cases", color="Prefecture"))
# グラフの種類を指定
g += geom_line()
g
Practical¶
実務的にはこれくらいのコードになる
Pros
- 後から要素を次々につなげていくことで
Cons
- パラメータの書き方がかなり癖があって調べながら出ないとよくわからない
- plotnine をワイルドカードインポートするのがR風
#practical
from plotnine import *
from mizani.breaks import date_breaks
from mizani.formatters import date_format
# データとプロット対象を指定
g = ggplot(data2, aes(x="Date", y='Newly confirmed cases', color="Prefecture"))
# グラフの種類を指定
g += geom_line()
# 見た目の調整を指定
g += theme(figure_size=(12, 4), text=element_text(family='Meiryo'))
g += theme(axis_text_x=element_text(rotation=-90, hjust=1))
g += labs(x='日付', y="新規感染者数", title="Covid19 日次感染者数")
g += scale_x_datetime(breaks=date_breaks("4 month"), labels=date_format("%Y-%m"))
g #描画
Pandas¶
Pandas にも matplotlib を利用した作図関数があり、単純なプロットなら Pandas だけで描画できる。
ほかのライブラリをインストールするのが難しい時などに使えそう。
高水準作図ライブラリは縦持ちデータをそのまま処理できるのが普通だが、Pandasは横持データしか処理できないようだ(詳しくは未調査)
こいつのおかげで「Pandasしか必要ないのに matplotlib がインストールされた?」という事になる。
minimum¶
#minimum
data_yoko.plot.line()
<Axes: xlabel='Date'>
Practical¶
実用的にはサイズを指定するくらいがせいぜいか?
#practical
data_yoko.plot.line(figsize=(14,4))
<Axes: xlabel='Date'>
minimum¶
最低限構成で描画してもかなりサマになる。横幅を目いっぱい使うのがPlotly流。
import plotly
# オフラインHTML出力のためのおまじない
plotly.offline.init_notebook_mode(connected=False)
import plotly.express as px
fig = px.line(data,
x = "Date",
y = "Newly confirmed cases",
color= "Prefecture",
)
fig.show()
Practical¶
Plotlyは日本語がそのまま通るのが美点
import plotly.express as px
fig = px.line(data,
x = "Date",
y = "Newly confirmed cases",
color= "Prefecture",
hover_name = "Prefecture",
title = "Covid19 日次感染者数",
width = 1200,
height = 450,
)
fig.show()
minimal¶
Plotly よりちょっと記述多め
import altair as alt
alt.renderers.enable('html') # Notebook の Html保存に必要
alt.Chart(data2).mark_line().encode(
x='Date',
y='Newly confirmed cases',
color='Prefecture',
)
Practical¶
- Pros
- 見た目はなかなか格好良い。
- Cons
- 凝ったことをしようとすると
Prefecture:NDate:Tみたいに型情報を指定してあげないといけないのがイマイチ - Plotly ではデフォルトで有効になっているホバーやズームをいちいち設定する必要がある
- 凝ったことをしようとすると
#practical
import altair as alt
alt.renderers.enable('html') #Notebook の Html 保存に必要
zoom = alt.selection_interval(bind='scales')
alt.Chart(data2).mark_line().encode(
x='Date',
y='Newly confirmed cases',
color='Prefecture',
strokeDash='Prefecture',
tooltip = [alt.Tooltip('Prefecture:N'),
alt.Tooltip('Date:T'),
alt.Tooltip('Newly confirmed cases:Q')
]
).properties(
width=1000,
height=400
).add_params(
zoom
).interactive()
HvPlot¶
HvPlot は Holoview をバックエンドとする高水準作図API。
Holoview はさらにバックエンドとして matplotlib/plotly/bokeh を選べるのが特徴で、HvPlot も同様。
表示されるグラフはbokeh / plotly で作図されている場合はインタラクティブに操作できる。
あまりよく調査できていないのだが、pandas の plot 関数を置き換えるような使い方をする。 APIは構造的でなく、いまひとつ洗練されていないように見受けられる。
bokeh をバックエンドに使う高水準作図ライブラリはほかに見つけれれていないので、bokeh が好きなら良いかもしれない。
公式ドキュメント¶
import hvplot
import hvplot.pandas
hvplot.extension('bokeh') #デフォルトではbokeh
data_yoko.hvplot.line(x="Date",
y=["Tokyo", "Osaka", "Aichi"],
width=1200,
height=400,
)
# import hvplot
import hvplot.pandas
hvplot.extension('plotly')
data_yoko.hvplot.line(x="Date",
y=["Tokyo", "Osaka", "Aichi"],
width=1200,
height=400,
)
BokehUserWarning: out of range integer may result in loss of precision BokehUserWarning: out of range integer may result in loss of precision
# import hvplot
import hvplot.pandas
hvplot.extension('matplotlib')
data_yoko.hvplot.line(x="Date",
y=["Tokyo", "Osaka", "Aichi"],
width=1200,
height=600,
)
低水準作図ライブラリ¶
Holoview¶
Holoview は bokeh/plotly/matplotlib をバックエンドに選択できる低水準?作図ライブラリ。
バックエンドが選択できる以外にどういうメリットがあるのかイマイチ良くわからない
公式ドキュメント¶
- https://holoviews.org/
- https://holoviews.org/user_guide/Customizing_Plots.html
- https://twitter.com/HoloViews
解説記事¶
# 作図指示の都合上、データフレームの形式を簡単にします
data_yoko2 = data_yoko.reset_index()
data_yoko2
| Prefecture | Date | Aichi | Osaka | Tokyo |
|---|---|---|---|---|
| 0 | 2020-01-16 | 0 | 0 | 0 |
| 1 | 2020-01-17 | 0 | 0 | 0 |
| 2 | 2020-01-18 | 0 | 0 | 0 |
| 3 | 2020-01-19 | 0 | 0 | 0 |
| 4 | 2020-01-20 | 0 | 0 | 0 |
| ... | ... | ... | ... | ... |
| 1204 | 2023-05-04 | 221 | 499 | 994 |
| 1205 | 2023-05-05 | 252 | 440 | 906 |
| 1206 | 2023-05-06 | 282 | 430 | 1062 |
| 1207 | 2023-05-07 | 863 | 1098 | 2345 |
| 1208 | 2023-05-08 | 425 | 547 | 1331 |
1209 rows × 4 columns
Holoviews + bokeh¶
bokeh との組み合わせが一番整備されているようである
import holoviews as hv
hv.extension('bokeh')
hv.Overlay([
hv.Curve((data_yoko2["Date"],data_yoko2["Tokyo"]), "Date", "Newly confirmed cases", label="Tokyo"),
hv.Curve((data_yoko2["Date"],data_yoko2["Osaka"]), label="Osaka"),
hv.Curve((data_yoko2["Date"],data_yoko2["Aichi"]), label="Aichi"),
]).opts(width=1200, height=400, legend_position='top_left')
Holoviews + Plotly¶
Plotly では凡例が出てこない
hv.extension('plotly')
hv.Overlay([
hv.Curve((data_yoko2["Date"],data_yoko2["Tokyo"]), "Date", "Newly confirmed cases", label="Tokyo"),
hv.Curve((data_yoko2["Date"],data_yoko2["Osaka"]), label="Osaka"),
hv.Curve((data_yoko2["Date"],data_yoko2["Aichi"]), label="Aichi"),
]).opts(width=1200, height=400, legend_position='top_left')
WARNING:param.main: Option 'legend_position' for Overlay type not valid for selected backend ('plotly'). Option only applies to following backends: ['bokeh', 'matplotlib']
BokehUserWarning: out of range integer may result in loss of precision BokehUserWarning: out of range integer may result in loss of precision
Holoviews + matplotlib¶
matplotlib ではピクセルで大きさの指定ができず、インチとアスペクト比の指定となる。
そこでAPIが共通化されていなくてどうするのだ、という印象。
hv.extension('matplotlib')
hv.Overlay([
hv.Curve((data_yoko2["Date"],data_yoko2["Tokyo"]), "Date", "Newly confirmed cases", label="Tokyo"),
hv.Curve((data_yoko2["Date"],data_yoko2["Osaka"]), label="Osaka"),
hv.Curve((data_yoko2["Date"],data_yoko2["Aichi"]), label="Aichi"),
]).opts(aspect=2.5, fig_inches=18, legend_position='top_left')
#minimal
import plotly.graph_objects as go
fig = go.Figure()
trace0 = go.Scatter(x = data_yoko2["Date"], y = data_yoko2["Tokyo"], mode = 'lines')
trace1 = go.Scatter(x = data_yoko2["Date"], y = data_yoko2["Aichi"], mode = 'lines')
trace2 = go.Scatter(x = data_yoko2["Date"], y = data_yoko2["Osaka"], mode = 'lines')
fig.add_trace(trace0)
fig.add_trace(trace1)
fig.add_trace(trace2)
fig.show()
#practical
import plotly.graph_objects as go
fig = go.Figure()
trace0 = go.Scatter(x = data_yoko2["Date"], y = data_yoko2["Tokyo"], mode = 'lines', name = 'Tokyo')
trace1 = go.Scatter(x = data_yoko2["Date"], y = data_yoko2["Aichi"], mode = 'lines', name = 'Aichi')
trace2 = go.Scatter(x = data_yoko2["Date"], y = data_yoko2["Osaka"], mode = 'lines', name = 'Osaka')
fig.add_trace(trace0)
fig.add_trace(trace1)
fig.add_trace(trace2)
fig.layout.update({'title': '新規感染者数推移'})
fig.layout.xaxis.update({'title': '日付'})
fig.layout.yaxis.update({'title': '新規感染者数'})
fig.update_layout(
autosize=False,
width=1200,
height=450)
fig.show()
#minimal
from bokeh.plotting import figure, show
from bokeh.io import output_notebook
output_notebook() #jupyter notebook 対応
p = figure()
p.line(data_yoko2.Date, data_yoko2.Tokyo)
p.line(data_yoko2.Date, data_yoko2.Aichi)
p.line(data_yoko2.Date, data_yoko2.Osaka)
show(p)
#plactical
from bokeh.plotting import figure, show
from bokeh.models import HoverTool
from bokeh.palettes import d3
from bokeh.io import output_notebook
output_notebook() #jupyter notebook 対応
# デフォルトで色分けしてくれないため色の設定を取ってくる必要がある
colors = d3["Category10"][10]
# ツールチップ周りの設定が異様に難しい。
hover_tool = HoverTool(
tooltips = [
( 'Date', '$x{%F}' ),
( 'Prefecture',''),
( '新規感染者数', '@y' ),
],
formatters={'$x': 'datetime'}
)
p = figure(title="新規感染者数の推移",
y_axis_label="新規感染者数",
x_axis_label="日付",
x_axis_type='datetime',
width=1200,
height=450)
p.line(data_yoko2.Date, data_yoko2.Tokyo, legend_label="Tokyo", line_color=colors[0])
p.line(data_yoko2.Date, data_yoko2.Aichi, legend_label="Aichi", line_color=colors[1])
p.line(data_yoko2.Date, data_yoko2.Osaka, legend_label="Osaka", line_color=colors[2])
p.add_layout(p.legend[0], 'right')
p.add_tools(hover_tool)
show(p)
#minimum
%matplotlib inline
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
ax.plot(data_yoko2.Date, data_yoko2.Tokyo, label="Tokyo")
ax.plot(data_yoko2.Date, data_yoko2.Aichi, label="Aichi")
ax.plot(data_yoko2.Date, data_yoko2.Osaka, label="Osaka")
plt.show()
#practical
%matplotlib inline
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize = [14,4])
ax.plot(data_yoko2.Date, data_yoko2.Tokyo, label="Tokyo")
ax.plot(data_yoko2.Date, data_yoko2.Aichi, label="Aichi")
ax.plot(data_yoko2.Date, data_yoko2.Osaka, label="Osaka")
ax.legend()
plt.title("新規感染者数の推移", fontname="Meiryo")
plt.xlabel("日付", fontname="Meiryo")
plt.ylabel("新規感染者数", fontname="Meiryo")
plt.xticks(rotation=-90)
plt.show()